package com.idanchuang.component.just.web.handler;

import com.idanchuang.component.base.JsonResult;
import com.idanchuang.component.base.exception.common.ErrorCode;
import com.idanchuang.component.base.exception.core.ExDefinition;
import com.idanchuang.component.base.exception.core.ExFactory;
import com.idanchuang.component.base.exception.core.ExType;
import com.idanchuang.component.base.exception.exception.AbstractException;
import com.idanchuang.component.base.exception.exception.BusinessException;
import com.idanchuang.component.just.web.config.ComponentConfig;
import com.idanchuang.component.logback.helper.LogContext;
import com.zoi7.component.core.exception.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
 * <p>
 * 全局异常处理
 * </p>
 *
 * @author yjy
 * 2019-11-02 10:37
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private final ComponentConfig componentConfig;

    @Autowired
    public GlobalExceptionHandler(ComponentConfig componentConfig) {
        this.componentConfig = componentConfig;
    }

    @ExceptionHandler(value = Exception.class)
    public JsonResult<Void> handleBadRequest(Exception exp, HttpServletResponse response) {
        loggerError(exp);
        // 符合异常规范
        if (exp instanceof AbstractException) {
            return ((AbstractException) exp).response();
        }

        String expClassName = exp.getClass().getName();
        String expSimpleName = exp.getClass().getSimpleName();
        // handle sentinel exception
        boolean feign429 = expClassName.startsWith("feign.FeignException") && exp.getMessage().contains("status 429");
        boolean self429 = expClassName.startsWith("com.alibaba.csp.sentinel.slots") &&
                ("AuthorityException".equals(expSimpleName) || "DegradeException".equals(expSimpleName)
                || "FlowException".equals(expSimpleName) || "ParamFlowException".equals(expSimpleName)
                || "SystemBlockException".equals(expSimpleName));
        if (feign429 || self429) {
            try {
                response.sendError(429);
            } catch (Exception e) {
                log.error("BlockException sendError failed", e);
            }
            return JsonResult.failure(ErrorCode.TOO_MANY_REQUEST, exp.getClass().getSimpleName());
        }

        // 请求方式异常
        if (exp instanceof HttpRequestMethodNotSupportedException) {
            return JsonResult.failure(ExFactory.of(ErrorCode.METHOD_NOT_ALLOWED));
        }
        // 超时异常
        if (exp instanceof TimeoutException) {
            return JsonResult.failure(ExFactory.of(ErrorCode.TIMEOUT));
        }
        // 参数校验异常
        if (exp instanceof BindException) {
            BindingResult bindingResult = ((BindException) exp).getBindingResult();
            if (bindingResult.hasErrors()) {
                String msg = bindingResult.getAllErrors().stream()
                        .map((e) -> {
                            if (e instanceof FieldError) {
                                FieldError fe = (FieldError) e;
                                return fe.getField() + ":" + e.getDefaultMessage();
                            } else {
                                return e.toString();
                            }
                        }).collect(Collectors.joining(","));
                return JsonResult.failure(ExFactory.of(ErrorCode.PARAM_ERROR, msg));
            }
        }
        if (exp instanceof MethodArgumentNotValidException) {
            BindingResult bindingResult = ((MethodArgumentNotValidException) exp).getBindingResult();
            if (bindingResult.hasErrors()) {
                String msg = bindingResult.getAllErrors().stream()
                        .map((e) -> {
                            if (e instanceof FieldError) {
                                FieldError fe = (FieldError) e;
                                return fe.getField() + ":" + e.getDefaultMessage();
                            } else {
                                return e.toString();
                            }
                        }).collect(Collectors.joining(","));
                return JsonResult.failure(ExFactory.of(ErrorCode.PARAM_ERROR, msg));
            }
        }
        // 参数异常
        if (exp instanceof ArgumentsInvalidException
                || exp instanceof IllegalArgumentException
                || exp instanceof MissingServletRequestParameterException) {
            return JsonResult.failure(ExFactory.of(ErrorCode.PARAM_ERROR, exp.getMessage()));
        }
        // 自定义配置未加载异常
        if (exp instanceof ConfigNotReadyException) {
            return JsonResult.failure(ExFactory.of(ErrorCode.SYSTEM_ERROR, exp.getMessage()));
        }
        // 自定义资源未找到
        if (exp instanceof NotFoundException) {
            return JsonResult.failure(ExFactory.of(ErrorCode.NOT_FOUND));
        }
        // 自定义未登入
        if (exp instanceof NotLoginException) {
            return JsonResult.failure(ExFactory.of(ErrorCode.UNAUTHORIZED));
        }
        // 自定义操作失败
        if (exp instanceof OperationFailedException) {
            return JsonResult.failure(ExFactory.of(ErrorCode.BUSINESS_ERROR, exp.getMessage()));
        }
        // 自定义服务不可用
        if (exp instanceof ServiceException) {
            return JsonResult.failure(ExFactory.of(ErrorCode.SERVICE_UNAVAILABLE));
        }
        return JsonResult.failure(ExFactory.of(ErrorCode.SYSTEM_ERROR, componentConfig.isUseDefaultExceptionMessage() ? componentConfig.getDefaultExceptionMessage() : exp.getMessage()));
    }

    /**
     * 打印异常信息
     */
    private void loggerError(Exception exp) {
        String logTitle = componentConfig.getGlobalExceptionLogTitle();
        String keyword = exp.getClass().getSimpleName();
        if (exp instanceof AbstractException) {
            ExDefinition exDefinition = ((AbstractException) exp).getExDefinition();
            if (exDefinition != null) {
                keyword += ":" + exDefinition.getCode();
            }
        }
        LogContext.setKeyword(keyword);
        // Normal BusinessException
        if (exp instanceof BusinessException) {
           logBusinessException(exp);
           return;
        }
        // AbstractException with ExType.BUSINESS_ERROR
        if (exp instanceof AbstractException) {
            ExType exType = ((AbstractException) exp).getExDefinition().getExType();
            if (ExType.BUSINESS_ERROR.equals(exType)) {
                logBusinessException(exp);
                return;
            }
        }
        // Exception about arguments
        if (exp instanceof MissingServletRequestParameterException
        || exp instanceof MethodArgumentNotValidException
        || exp instanceof ArgumentsInvalidException
        || exp instanceof BindException
        || exp instanceof IllegalArgumentException) {
            logBusinessException(exp);
            return;
        }
        // Other Exception
        log.error(logTitle, exp);
    }

    /**
     * 打印业务异常
     * @param exp 异常
     */
    private void logBusinessException(Exception exp) {
        String logTitle = componentConfig.getGlobalExceptionLogTitle();
        String logInfo = logTitle + ": " + exp.getMessage();
        switch (componentConfig.getBusinessLogLevel().toLowerCase()) {
            case "debug":
                if (componentConfig.getBusinessLogTrace()) {
                    log.debug(logTitle, exp);
                } else {
                    log.debug(logInfo);
                }
                break;
            case "info":
                if (componentConfig.getBusinessLogTrace()) {
                    log.info(logTitle, exp);
                } else {
                    log.info(logInfo);
                }
                break;
            case "error":
                if (componentConfig.getBusinessLogTrace()) {
                    log.error(logTitle, exp);
                } else {
                    log.error(logInfo);
                }
                break;
            default:
                if (componentConfig.getBusinessLogTrace()) {
                    log.warn(logTitle, exp);
                } else {
                    log.warn(logInfo);
                }
                break;
        }
    }

}
